#include "../Misc/part3_shared.h"

int read_from_console(char *input, int max_input_size) {

	int i, status;
	int c;

	i = 0;
	status = 0;
	while ((c = getchar()) != EOF) {

		/* check if finished */
		if ((i < max_input_size) && (c == '\n' || c == '\0')) {
			input[i] = '\0';
			break;
		}

		/* input next char */
		if (!status) {
			input[i] = c;
			i++;
		}

		/* input too long, continue until you get to the end*/
		if ( i >= max_input_size) {
			status = 1;
			continue;
		}
	}

	if (feof(stdin) && (i < max_input_size) ) {
		input[i] = '\0';
	}

	if (status) {
		fprintf(stderr, "Error: input was too big... try again\n");
		return -1;
	}

	return 1;
}


int main(int argc, char **argv) {

	DEHT *ht;
	RULE rule;
	INI_PARAMS ini_params;
	BasicHashFunctionPtr crypt_func;
	int cryptHashSize;
	char** lexicon;
	LONG_INDEX_PROJ lexicon_size = 0;
	int status=0;
	LONG_INDEX_PROJ *seeds = NULL;
	unsigned char curHash[MAX_BINARY_HASH];

	char input[MAX_INTPUT_SIZE + 1];
	int quit = 0;

	unsigned char target_key[MAX_BINARY_HASH];
	char hexa_target_key[MAX_HASH_SIZE];
	
	char NewPassword[MAX_INTPUT_SIZE];
	int NewPassword_length;
	
	int multi_length;
	unsigned char multi_data[MAX_PASSWORD_LENGTH*MAX_RETURN_PW + 1];
	unsigned char *multi_point[MAX_RETURN_PW + 1];

	int i,j,w;
	LONG_INDEX_PROJ k;
	int found = 0;

	if (argc != 2)
	{
		fprintf(stderr, "Error: Usage crack_rainbow_table <preferences filename>\n");
		exit(1);
	}

	/* load .ini file to struct */
	ini_params = get_params_from_INI_file(argv[1]);
	/* check params */
	if (!strcmp(ini_params.HashFunction, "MD5")) {
		crypt_func = MD5BasicHash;
		cryptHashSize = MD5_OUTPUT_LENGTH_IN_BYTES;
	} else if (!strcmp(ini_params.HashFunction, "SHA1")) {
		crypt_func = SHA1BasicHash;
		cryptHashSize = SHA1_OUTPUT_LENGTH_IN_BYTES;
	} else {
		fprintf(stderr, "Error: Hash %s is not supported\n", argv[1]);
		exit(1);
	}

	if (ini_params.ChainLength < 1) {
		fprintf(stderr, "Error: rainbow_chain_length in %s is %d. It should be positive number\n",
				argv[1], ini_params.ChainLength);
		exit(1);
	}

	if (ini_params.NumOfHashEntries < 1) {
		fprintf(stderr, "Error: entries_in_hash_table in %s is %d. It should be a positive number\n",
				argv[1], ini_params.NumOfHashEntries);
		exit(1);
	}

	if (ini_params.ElementsInBucket < 1) {
		fprintf(stderr, "Error: bucket_block_length in %s is %d. It should be a positive number\n",
				argv[1], ini_params.ElementsInBucket);
		exit(1);
	}

	/* create lexicon */
	lexicon = load_lexicon(ini_params.LexiconName, &lexicon_size);
	status = create_rule(&rule, ini_params.Rule, lexicon, lexicon_size);
	if (status == -1) {
		free_lexicon(lexicon, lexicon_size);
		exit(1);
	} 

	/* load seed and DEHT */
	ht = load_DEHT_from_files(ini_params.OutputFilePrefix,cryptHashToTable,cryptHashTo64bit);
	if (ht == NULL)
	{
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}
	
	status = read_DEHT_pointers_table(ht);
	if (status == DEHT_STATUS_FAIL){
		lock_DEHT_files(ht);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}

	seeds = (LONG_INDEX_PROJ*) malloc( ini_params.ChainLength * sizeof(LONG_INDEX_PROJ));
	if (seeds == NULL){
		fprintf(stderr, "Error: not enough memory\n");
		lock_DEHT_files(ht);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}

	status = read_DEHT_Seed(ht, seeds, ini_params.ChainLength);
	if (status == DEHT_STATUS_FAIL)
	{
		free(seeds);
		lock_DEHT_files(ht);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}

	/* start main loop */
	while(!quit) {
		
		printf(">>");
		/* read input, if error or quit continue */
		status = read_from_console(input, MAX_INTPUT_SIZE);
		if (status == -1){
			continue;
		}
		if (!strcmp(input, "quit")) {
			quit = 1;
			continue;
		}
		if (!strcmp(input, "")) {
			continue;
		}

		/* if password recieved hash it, else get binary key */
		if (*input == '!') {
			cryptHash(crypt_func, input+1, target_key);
			binary2hexa(target_key, cryptHashSize, hexa_target_key, MAX_HASH_SIZE);
			printf("\tIn hexa password is %s\n", hexa_target_key);
			status = 0;
		} else {
			status = hexa2binary(input, target_key, MAX_HASH_SIZE);
		}

		/* check command and start cracking */
		if (status == -1) {
			fprintf(stderr, "Commands are either hexa, !password or quit.\n");
			continue;
		} 
		else {
		found = 0;
		/* start cracking */
			for (j = ini_params.ChainLength; j >= 0; j--){
				/* exit for if finished */
				if (found == 1){
					break;
				}
				/* guess that our password is in j location */
				memcpy(curHash, target_key, cryptHashSize);
				/* go down the chain */
				for (i=j; i<ini_params.ChainLength; i++){
					k = pseudo_random_function(curHash, cryptHashSize, seeds[i]) % rule.number_of_possible_passwords;
					generate_kth_password(k,NewPassword, rule);
					cryptHash(crypt_func, NewPassword, curHash);
				}
				/* make querry in deht */
				multi_length = mult_query_DEHT(ht, curHash, cryptHashSize, multi_data, MAX_INTPUT_SIZE*MAX_RETURN_PW + 1 , multi_point, MAX_RETURN_PW + 1);
				if (multi_length == DEHT_STATUS_FAIL){
					free(seeds);
					lock_DEHT_files(ht);
					free_lexicon(lexicon, lexicon_size);
					free_rule(&rule);
					exit(1);
				}
				/* not found - continue to next j */
				if (multi_length == 0){
					continue;
				}
				
				/* check multi_data array */
				for (w=0; w<multi_length; w++){
					NewPassword_length = multi_point[w + 1] - multi_point[w];
					if (NewPassword_length > MAX_INTPUT_SIZE){ 
						continue;
					}
					/* try  password */
					memcpy(NewPassword, multi_point[w], NewPassword_length);
					NewPassword[NewPassword_length] = '\0';

					/* go j-1 steps down the current chain */
					for (i = 0; i < j; i++) {
						cryptHash(crypt_func, NewPassword, curHash);
						k = pseudo_random_function(curHash, cryptHashSize, seeds[i]) % rule.number_of_possible_passwords;
						generate_kth_password(k,NewPassword, rule);
					}
					
					/* check this password */
					cryptHash(crypt_func, NewPassword, curHash);
					if (memcmp(curHash, target_key, cryptHashSize) == 0) {
						printf("Try to login with password \"%s\"\n", NewPassword);
						found = 1; 
						break;
					}	
				}
			}
			if (!found) {
				printf("Sorry but this hash doesn't appears in pre-processing\n");
			}

		}
	}
	
	status = write_DEHT_pointers_table(ht);
	if (status == DEHT_STATUS_FAIL){
		free(seeds);
		lock_DEHT_files(ht);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}

	free(seeds);
	lock_DEHT_files(ht);
	free_lexicon(lexicon, lexicon_size);
	free_rule(&rule);
	return 0;
}


